package app_kvEcs;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;

import javax.swing.plaf.basic.BasicInternalFrameTitlePane.SystemMenuBar;

import org.apache.log4j.Logger;

import common.messages.KVAdminMessage;
import common.messages.MD5Hash;
import common.messages.MetaData;
import communicationLogic.ClientSocket;
import app_kvServer.KVServer;

/**
 * java ­jar ECS.jar ecs.config
 *  ECS provides the following interface to the ECSClient application
 * 
 * The ECS is bootstrapped with a configuration file that specifies the IP 
 * addresses
 * and ports of n storage servers under its control. These storage servers
 *  provide
 * the KVServer interface available at the specified IP and port. The storage
 *  servers are launched by the ECS through SSH calls.
 * 
 */

public class ECS {
    private Logger logger = Logger.getRootLogger();
    private KVAdminMessage kvadmin;
    private HashMap<String, String> map;
    public HashMap<String, String> activeMap;
    private HashMap<String, String> tempMap;
    private int numberOfServers = 0;
    private int readyServers = 0;
    private int runningServers = 0;
    private ClientSocket clientSocket;

    /**
     * Constructs a ECServer at given port
     * 
     * @param configuration
     *            file name
     */
    public ECS(String confile) {
        try {
            FileReader reader = new FileReader(confile);
            BufferedReader br = new BufferedReader(reader);
            String s1 = null;
            map = new HashMap<String, String>();
            // meta = new MetaData(activeMap);
            // activeMap = meta.getActiveMap();
            activeMap = new HashMap<String, String>();
            while ((s1 = br.readLine()) != null) {
                String[] nodeInfo = s1.split(" ");
                String ip = nodeInfo[1];
                String port = nodeInfo[2];
                String key = ip + ":" + port;
                String value = new MD5Hash().doHash(key);
                map.put(key, value);
                logger.info(key + " " + value);
            }
            numberOfServers = map.size();
            tempMap = (HashMap<String, String>) map.clone();

            br.close();
            reader.close();
        } catch (IOException e) {
            System.out.println("Please check your ecs.conf");
        }

    }

    /**
     * launch m storage server nodes through SSH calls.
     * 
     * 1.Compute key­range partitioning for initial setup (initial meta­data)
     * 2.Assemble meta­data 3.Launch nodes with initial meta­data
     * 
     * Randomly choose <numberOfNodes> servers from the available
     * machines and start the KVServer by issuing a SSH call to the
     * respective machine. This call launches the server. You can
     * assume that the KVServer.jar is located in the same directory as
     * the ECS. All servers are initialized with the meta­data and remain
     * in state stopped.
     * 
     * @param numberOfNodes
     */

    public void initService(int numberOfNodes) {
        readyServers = numberOfServers - runningServers;
        // logger.info("readyServers: "+readyServers);
        logger.info("start to init");
        HashMap<String, String> tempActiveMap = new HashMap<String, String>();

        if (numberOfNodes > readyServers) {
            logger.info("We only have " + readyServers + " nodes");
            throw new IllegalArgumentException();
        } else {
            Date date = new Date();
            long timeMill = date.getTime();
            Random random = new Random(timeMill);
            for (int i = 0; i < numberOfNodes; i++) {
                String keyArry[] = (String[]) tempMap.keySet().toArray(
                        new String[0]);
                int r = random.nextInt(readyServers);
                // logger.info(keyArry[r]);
                // logger.info(tempMap.get(keyArry[r]));
                tempActiveMap.put(keyArry[r], tempMap.get(keyArry[r]));
                tempMap.remove(keyArry[r]);
                readyServers--;
            }
            System.out.println("#servers " + numberOfServers);

            // Assemble meta­data
            activeMap.putAll(tempActiveMap);
            // kvadmin = new KVAdminMessage(activeMap);
            System.out.println("#try to active " + tempActiveMap.size()
                    + " servers ");
            System.out.println("#Idil servers " + tempMap.size());

            // java -jar ms3-server.jar
            Iterator<String> iter = tempActiveMap.keySet().iterator();
            while (iter.hasNext()) {
                // Launch nodes with initial meta­data
                Process proc;
                String key = iter.next();
                String value = tempActiveMap.get(key);
                String[] ipPort = key.split(":");
                String ip = ipPort[0];
                String port = ipPort[1];

                // For Windows
                 String cmd = "cmd /c start java " + "-jar " +
                 "ms3-server.jar " + port + " \n";
                // For Linux
                //String cmd = "sh script.sh " + ip + " " + port;
                logger.info(cmd);
                Runtime run = Runtime.getRuntime();

                try {
                    proc = run.exec(cmd);
                    Thread.sleep(500);
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

                try {
                    logger.info("Tring to connect ip " + ip);
                    logger.info("Tring to connect port " + port);

                    clientSocket = new ClientSocket(ip, Integer.parseInt(port));
                    clientSocket.connect();
                    kvadmin = new KVAdminMessage();
                    kvadmin.setCommand("init");
                    kvadmin.setMessageTag("2");
                    kvadmin.setMetaList(KVAdminMessage.sortMetaData(activeMap));
                    byte[] message = kvadmin.toByteArray(kvadmin);
                    clientSocket.send(message);
                    clientSocket.disConnect();

                    logger.info("sent init message");

                } catch (NumberFormatException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            runningServers += numberOfNodes;
            logger.info("#Now running: " + activeMap.size());
            System.out.println("#Now running: " + activeMap.size());
            logger.info("#Success init " + numberOfNodes + " servives");
            System.out.println("#Success init " + numberOfNodes + " servives");

        }
    }

    /**
     * Starts the storage service by calling start() on all KVServer
     * instances that participate in the service.
     */
    public void start() {
        System.out.println("#begin to start providing servers");
        broadCastMetaData("start");
        // After all servers acknowledged the meta­data reception, the ECS should be able activate the
        // storage service by sending messages to all participating servers and calling their start()­method.
        // TODO wait all ACK

        System.out.println("#Success start " + runningServers + " servives");
    }

    /**
     * Stops the service; all participating KVServers are stopped for
     * processing client requests but the processes remain running.
     */
    public void stop() {
        System.out.println("#begin to stop providing servers");
        broadCastMetaData("stop");
        System.out.println("#Success stop the servives");
    }

    /**
     * Stops all server instances and exits the remote processes.
     */
    public void shutDown() {
        logger.info("#begin to shutdown the servers");
        broadCastMetaData("shutdown");
        logger.info("#Success shutdown the servers");
    }

    /**
     * Add a new node to the storage service at an arbitrary position.
     * 
     * Compute key­range position of the server within the service ii.
     * Launch node at a given IP:Port with updated meta­data iii.
     * Inform neighbour to initiate key­value hand­off iv.
     * Update meta­data of affected storage nodes v.
     * Read requests are always served
     * 
     */

    /**
     *  Set write lock (lockWrite()) on the successor(old) node; 
     * Invoke the transfer
     *  of the affected data items (i.e., the range of keys that was previously
     * handled
     *  by the successor node) to the new storage server. The data that is
     *  transferred
     * should not be deleted immediately to be able to serve read requests
     *  in the mean time ○ successor.moveData(range, newServer) 
     * When all affected
     *  data has been transferred (i.e., the successor sent back a notification
     * to the ECS) ○
     * Send a meta­data update to all storage servers (to inform them
     *  about their new responsibilities) ○
     * Release the write lock on the successor
     *  node and finally remove the data items
     * that are no longer handled by this server
     * 
     * @param ip
     * @param port
     */
    public void addNode() {
        if (runningServers < numberOfServers) {
            String keyArry[] = (String[]) tempMap.keySet().toArray(
                    new String[0]);
            Random random = new Random();
            int r = random.nextInt(readyServers);
            logger.info("adding node: " + keyArry[r]);
            activeMap.put(keyArry[r], tempMap.get(keyArry[r]));
            tempMap.remove(keyArry[r]);
            readyServers--;
            runningServers++;
            String activeKeyArry[] = (String[]) activeMap.keySet().toArray(
                    new String[0]);

            String sucessorKey = "";
            String[] ipPort = null;
            String ip = "";
            String port = "";
            for (int i = 0; i < activeKeyArry.length; i++) {
                if (activeKeyArry[i].equalsIgnoreCase(keyArry[r])) {
                    if (i == 0) {
                        sucessorKey = activeKeyArry[i + 1];
                        ipPort = sucessorKey.split(":");
                        ip = ipPort[0];
                        port = ipPort[1];
                        System.out.println("#The sucessor is " + sucessorKey
                                + " " + activeMap.get(sucessorKey));
                        System.out
                                .println("#The fater is "
                                        + (activeKeyArry[activeMap.size() - 1])
                                        + " "
                                        + activeMap.get(activeKeyArry[activeMap
                                                .size() - 1]));
                    } else if (i == (activeMap.size() - 1)) {
                        sucessorKey = activeKeyArry[0];
                        ipPort = sucessorKey.split(":");
                        ip = ipPort[0];
                        port = ipPort[1];
                        System.out.println("#The sucessor is " + sucessorKey
                                + " " + activeMap.get(sucessorKey));
                        System.out.println("#The father is "
                                + activeKeyArry[i - 1]);

                    } else {
                        sucessorKey = activeKeyArry[i + 1];
                        ipPort = sucessorKey.split(":");
                        ip = ipPort[0];
                        port = ipPort[1];
                        System.out.println("#The sucessor is " + sucessorKey
                                + " " + activeMap.get(sucessorKey));
                        System.out.println("#The father is "
                                + activeKeyArry[i - 1]);

                    }
                }

            }

            // java -jar ms3-server.jar
            // Launch nodes with initial meta­data
            Process proc;
            String key = keyArry[r];
            String value = tempMap.get(keyArry[r]);
            String[] runipPort = key.split(":");
            String runip = runipPort[0];
            String runport = runipPort[1];
            // For Windows
             String cmd = "cmd /c start java " + "-jar " +
             "ms3-server.jar " + runport + " \n";
            // For Linux
            //String cmd = "sh script.sh " + ip + " " + port;
            logger.info(cmd);
            Runtime run = Runtime.getRuntime();

            try {
                proc = run.exec(cmd);
                Thread.sleep(1000);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("#We are going to notify " + sucessorKey);

            // Assemble meta­data
            // kvadmin = new KVAdminMessage(activeMap);
            System.out.println("#Idil servers " + tempMap.size());

            // notify sucessorKey we have added a node
            List<MetaData> metaDatas = KVAdminMessage.sortMetaData(activeMap);
            for (MetaData m : metaDatas) {
                String tempkey = m.getIp() + ":" + m.getPort();
                if (tempkey.equalsIgnoreCase(sucessorKey)) {
                    try {
                        clientSocket = new ClientSocket(m.getIp(),
                                Integer.parseInt(m.getPort()));
                        System.out.println("Adding node IP is: " + m.getIp());
                        System.out.println("Adding node Port is: "
                                + m.getPort());

                        clientSocket.connect();
                        kvadmin = new KVAdminMessage();
                        kvadmin.setCommand("addNode");
                        kvadmin.setMessageTag("2");
                        kvadmin.setMetaList(KVAdminMessage
                                .sortMetaData(activeMap));
                        byte[] message = kvadmin.toByteArray(kvadmin);
                        clientSocket.send(message);
                        clientSocket.disConnect();

                    } catch (NumberFormatException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println("#sent metaData message after adding one node");
                }
            }
            broadCastMetaData("update");
        } else {
            System.out.println("#We have already " + runningServers
                    + " running and no idle servers");
        }

    }

    /**
     * Remove a node from the storage service at an arbitrary position.
     * Remove a node i. Re­compute key­range position for affected storage nodes
     * ii. Inform neighbour to initiate key­value hand­off iii.
     * Update meta­data of affected storage nodes iv.
     * Shut down node at the given IP:Port v. Read requests are always served
     * 
     */
    public void removeNode() {
        // randomly chose one running server keyArry[r]
        Random random = new Random();
        String keyArry[] = (String[]) activeMap.keySet().toArray(new String[0]);
        int r = random.nextInt(runningServers);
        // logger.info(tempMap.get(keyArry[r]));
        String sucessorKey = "";
        String[] ipPort = null;
        String ip = "";
        String port = "";
        System.out.println("#We are going to remove " + keyArry[r]);

        if (r == 0) {
            System.out.println("You ONLY have one node running!");
            System.out.println("Are you sure you want to remove it?");
            /**
            sucessorKey = keyArry[r + 1];
            ipPort = sucessorKey.split(":");
            ip = ipPort[0];
            port = ipPort[1];
            System.out.println("#The sucessor is " + sucessorKey + " "
                    + activeMap.get(sucessorKey));
            System.out.println("#The fater is "
                    + (keyArry[activeMap.size() - 1]) + " "
                    + activeMap.get(keyArry[activeMap.size() - 1]));
                    **/
        } else if (r == (activeMap.size() - 1)) {
            sucessorKey = keyArry[0];
            ipPort = sucessorKey.split(":");
            ip = ipPort[0];
            port = ipPort[1];
            System.out.println("#The sucessor is " + sucessorKey + " "
                    + activeMap.get(sucessorKey));
            System.out.println("#The father is " + keyArry[r - 1]);

        } else if (activeMap.size() == 1) {
            System.out.println("#We only have one node "
                    + activeMap.keySet().toString());
            System.out.println("#If you remove it, all data will be deleted");
        } else {
            sucessorKey = keyArry[r + 1];
            ipPort = sucessorKey.split(":");
            ip = ipPort[0];
            port = ipPort[1];
            System.out.println("#The sucessor is " + sucessorKey + " "
                    + activeMap.get(sucessorKey));
            System.out.println("#The father is " + keyArry[r - 1]);

        }
        
        //add 1.29
        sucessorKey = keyArry[r];
        ipPort = sucessorKey.split(":");
        ip = ipPort[0];
        port = ipPort[1];
        
        
        activeMap.remove(keyArry[r]);
        tempMap.put(keyArry[r], activeMap.get(keyArry[r]));
        // send removeNode command to successor
        try {
            clientSocket = new ClientSocket(ip, Integer.parseInt(port));
            clientSocket.connect();
            kvadmin = new KVAdminMessage();
            kvadmin.setCommand("removeNode");
            kvadmin.setMessageTag("2");
            kvadmin.setMetaList(KVAdminMessage.sortMetaData(activeMap));
            byte[] message = kvadmin.toByteArray(kvadmin);
            clientSocket.send(message);
            clientSocket.disConnect();
        } catch (NumberFormatException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        // send shutdown command to the removedNode
        String shutIpPort[] = keyArry[r].split(":");
        try {
            clientSocket = new ClientSocket(shutIpPort[0],
                    Integer.parseInt(shutIpPort[1]));
            clientSocket.connect();
            kvadmin = new KVAdminMessage();
            kvadmin.setCommand("shutdown");
            kvadmin.setMessageTag("2");
            kvadmin.setMetaList(KVAdminMessage.sortMetaData(activeMap));
            byte[] message = kvadmin.toByteArray(kvadmin);
            clientSocket.send(message);
            clientSocket.disConnect();
        } catch (NumberFormatException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        broadCastMetaData("update");
        readyServers++;
        runningServers--;
        System.out.println("#sent metaData message after removing one node");
        // TODO shutdown the server

    }

    private void broadCastMetaData(String command) {
        Iterator<String> iter = activeMap.keySet().iterator();
        while (iter.hasNext()) {
            String key = iter.next();
            String[] ipPort = key.split(":");
            String ip = ipPort[0];
            String port = ipPort[1];
            try {
                System.out.println("Trying to connect ip " + ip);
                System.out.println("Trying to connect port " + port);
                clientSocket = new ClientSocket(ip, Integer.parseInt(port));

                clientSocket.connect();
                kvadmin = new KVAdminMessage();
                kvadmin.setCommand(command);
                kvadmin.setMessageTag("2");
                kvadmin.setMetaList(KVAdminMessage.sortMetaData(activeMap));
                byte[] message = kvadmin.toByteArray(kvadmin);
                clientSocket.send(message);
                
                //clientSocket.disConnect();
            } catch (NumberFormatException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.println("#sent metaData message after "+command+" one node");
        System.out.println("#Update metatData finished##");
    }

}
